# Copyright 2004-2008 Roman Yakovenko.
# Distributed under the Boost Software License, Version 1.0. (See
# accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

"""defines interface for all classes that writes :class:`code_creators.module_t` to file(s)"""

import os
import time
import codecs
import md5sum_repository
from pyplusplus import utils
from pyplusplus import _logging_
from pyplusplus import code_creators
from pyplusplus import code_repository

class writer_t(object):
    """Base class for all module/code writers.

    All writers should have similar usage::

      w = writer_class(module, file, ...)
      w.write()
    """
    logger = _logging_.loggers.file_writer

    def __init__(self, extmodule, files_sum_repository=None, encoding='ascii'):
        object.__init__(self)
        self.__extmodule = extmodule
        self.__files_sum_repository = files_sum_repository
        self.__encoding=encoding
        if None is files_sum_repository:
            self.__files_sum_repository = md5sum_repository.dummy_repository_t()
        self.__exposed_decls_db = utils.exposed_decls_db_t()
        self.__exposed_decls_db.register_decls( extmodule.global_ns
                                                , extmodule.specially_exposed_decls )

    def makedirs_for_file( self, file_path ):
        destination_dir = os.path.dirname( file_path )
        if destination_dir == "":
            return

        if not os.path.exists( destination_dir ):
            os.makedirs( destination_dir )

    @property
    def encoding( self ):
        """encoding name used to write generated code to files"""
        return self.__encoding

    @property
    def extmodule(self):
        """The root of the code creator tree ( code_creators.module_t )"""
        return self.__extmodule

    @property
    def files_sum_repository( self ):
        return self.__files_sum_repository

    def write(self):
        """ Main write method.  Should be overridden by derived classes. """
        raise NotImplementedError()

    @staticmethod
    def create_backup(fpath):
        """creates backup of the file"""
        if not os.path.exists( fpath ):
            return
        backup_fpath = fpath + '~'
        if os.path.exists( backup_fpath ):
            os.remove( backup_fpath )
        os.rename( fpath, backup_fpath )

    def write_code_repository(self, dir):
        """creates files defined in :mod:`code_repository` package"""
        visited = set()
        system_files = self.extmodule.get_system_files( recursive=True )
        for cr in code_repository.all:
            if cr.file_name not in system_files:
                #check whether file from code repository is used
                continue
            if cr.file_name in visited:
                continue

            destination_path = os.path.normpath( os.path.join( dir, cr.file_name ) )
            self.makedirs_for_file( destination_path )
            self.write_file( destination_path, cr.code )
            visited.add( cr.file_name )

            for fdepend in code_repository.i_depend_on_them( cr.file_name ):
                if fdepend.file_name not in visited:
                    destination_path = os.path.normpath( os.path.join( dir, fdepend.file_name ) )
                    self.makedirs_for_file( destination_path )
                    self.write_file( destination_path, fdepend.code )
                    visited.add( fdepend.file_name )

    @staticmethod
    def write_file( fpath, content, files_sum_repository=None, encoding='ascii' ):
        """Write a source file.

        This method writes the string content into the specified file.
        An additional fixed header is written at the top of the file before
        content.

        :param fpath: File name
        :type fpath: str
        :param content: The content of the file
        :type content: str
        """
        fname = os.path.split( fpath )[1]
        writer_t.logger.debug( 'write code to file "%s" - started' % fpath )
        start_time = time.clock()
        fcontent_new = []
        if os.path.splitext( fpath )[1] == '.py':
            fcontent_new.append( '# This file has been generated by Py++.' )
        else:
            fcontent_new.append( '// This file has been generated by Py++.' )
        fcontent_new.append( os.linesep * 2 )
        fcontent_new.append( content )
        fcontent_new.append( os.linesep ) #keep gcc happy
        fcontent_new = ''.join( fcontent_new )
        if not isinstance( fcontent_new, unicode ):
            fcontent_new = unicode( fcontent_new, encoding )

        new_hash_value = None
        curr_hash_value = None
        if files_sum_repository:
            new_hash_value  = files_sum_repository.get_text_value( fcontent_new )
            curr_hash_value = files_sum_repository.get_file_value( fname )
            if new_hash_value == curr_hash_value:
                writer_t.logger.debug( 'file was not changed( hash ) - done( %f seconds )'
                                       % ( time.clock() - start_time ) )
                return

        if None is curr_hash_value and os.path.exists( fpath ):
            #It could be a first time the user uses files_sum_repository, don't force him
            #to recompile the code
            #small optimization to cut down compilation time
            f = codecs.open( fpath, 'rb', encoding )
            fcontent = f.read()
            f.close()
            if fcontent == fcontent_new:
                writer_t.logger.debug( 'file was not changed( content ) - done( %f seconds )'
                                       % ( time.clock() - start_time ) )
                return

        writer_t.logger.debug( 'file changed or it does not exist' )

        writer_t.create_backup( fpath )
        f = codecs.open( fpath, 'w+b', encoding )
        f.write( fcontent_new )
        f.close()
        if new_hash_value:
            files_sum_repository.update_value( fname, new_hash_value )
        writer_t.logger.info( 'file "%s" - updated( %f seconds )' % ( fname, time.clock() - start_time ) )

    def get_user_headers( self, creators ):
        headers = []
        creators = filter( lambda creator: isinstance( creator, code_creators.declaration_based_t )
                           , creators )
        map( lambda creator: headers.extend( creator.get_user_headers() )
             , creators )
        return code_creators.code_creator_t.unique_headers( headers )

    def save_exposed_decls_db( self, file_path ):
        self.__exposed_decls_db.save( file_path )


